#include "stdafx.h"
#include "SendMessageErrorTestSuite.h"
#include "TestActiveObject.h"

namespace CO { namespace SpiFoundation { namespace RoomFW {

using namespace ErrorHandling;
using namespace SpiControl::Protocol;

SendMessageErrorTestSuite::SendMessageErrorTestSuite(void)
{
	LOG_TEST_ADD(SendMessageErrorTestSuite::empty)
#ifndef _DEBUG // Many of the errors are asserted as well, so only test release mode.
	LOG_TEST_ADD(SendMessageErrorTestSuite::testEndpoints)
#endif
	LOG_TEST_ADD(SendMessageErrorTestSuite::testMessageHandler)
#ifndef _DEBUG // Many of the errors are asserted as well, so only test release mode.
	LOG_TEST_ADD(SendMessageErrorTestSuite::testReasons)
	LOG_TEST_ADD(SendMessageErrorTestSuite::testPortals)
#endif
}

void SendMessageErrorTestSuite::setup()
{
	LoggedTestSuite::setup();
}

void SendMessageErrorTestSuite::tear_down()
{
	LoggedTestSuite::tear_down();
}

void SendMessageErrorTestSuite::empty(void)
{
}

//
// Test that endpoints are throwing the correct exceptions.
//
class ErrorResponder : public ActiveObject::MessageHandler
{
public:
	ErrorResponder(ActiveObject* parent) :
	  ActiveObject::MessageHandler(parent)
	{}
	virtual bool handle(RoomMessageSP message)
	{
		throw std::runtime_error("whatever");
	}
	typedef boost::shared_ptr<ErrorResponder> Ptr;
};

void SendMessageErrorTestSuite::testEndpoints(void)
{
	try
	{
		//
		// Test local endpoint
		//

		// Use a generic test object that does nothing without message handlers.
		TestActiveObjectSP tao(new TestActiveObject("tao"));

		// Create a message handler that throws an exception when it gets a message
		ErrorResponder::Ptr mh1(new ErrorResponder(tao.get()));
		tao->mapMessageHandler(TestActiveInterface::PORT_MY_PORT_1, mh1);

		// Create a local endpoint.
		LocalEndPoint lep(tao.get(), PortIndex(TestActiveInterface::PORT_MY_PORT_1, 0));

		// Verify contract violation failure.
		TEST_ASSERT(SendMessageError::SMER_CONTRACT_VIOLATION == lep.acceptMessage(GenericTestProtocol::OUT_VOID_MESSAGE1, IPayloadSP(), "Test", IEndPointSP()));

		// Start and error the object.
		TEST_ASSERT(tao->start(DWait));
		TEST_ASSERT(SendMessageError::SMER_NONE == lep.acceptMessage(GenericTestProtocol::IN_VOID_MESSAGE1, IPayloadSP(), "Test", IEndPointSP()));
		TEST_ASSERT(tao->wait(DWait));

		// Verify receiver errored exceptions.
		TEST_ASSERT(SendMessageError::SMER_RECEIVER_ERRORED == lep.acceptMessage(GenericTestProtocol::IN_VOID_MESSAGE1, IPayloadSP(), "Test", IEndPointSP()));

		// Create an invoke endpoint.
		LocalEndPoint iep(tao.get(), PortIndex(TestActiveInterface::PORT_MY_PORT_1, 0));

		// Verify contract violation failure.
		TEST_ASSERT(SendMessageError::SMER_CONTRACT_VIOLATION == iep.acceptMessage(GenericTestProtocol::OUT_VOID_MESSAGE1, IPayloadSP(), "Test", IEndPointSP()));

		// Start and error the object.
		TEST_ASSERT(tao->start(DWait));
		try
		{
			iep.acceptMessage(GenericTestProtocol::IN_VOID_MESSAGE1, IPayloadSP(), "Test", IEndPointSP());
		}
		catch (SendMessageError& e)
		{
			TEST_ASSERT_MSG(false, e.what());
		}
		TEST_ASSERT(tao->wait(DWait));

		// Verify receiver errored exceptions.
		TEST_ASSERT(SendMessageError::SMER_RECEIVER_ERRORED == iep.acceptMessage(GenericTestProtocol::IN_VOID_MESSAGE1, IPayloadSP(), "Test", IEndPointSP()));
	}
	catch (SpiFoundation::RoomFW::SendMessageError& e) // Messaging error.
	{
		G_LOG_0(SpiFoundation::RoomFW::LV_ERROR, e.what());
		TEST_ASSERT_MSG(false, e.what());
	}
	catch (std::runtime_error& e) // Binding error.
	{
		G_LOG_0(SpiFoundation::RoomFW::LV_ERROR, e.what());
		TEST_ASSERT_MSG(false, e.what());
	}
	catch (...) // What the heck error.
	{
		G_LOG_0(SpiFoundation::RoomFW::LV_ERROR, "Unhandled Exception");
		TEST_ASSERT_MSG(false, "Unhandled Exception");
	}
}

//
// MessageHandler test
//
//	1)  Create a message handler that responds to one message id with another and a constant int payload
//	2)  Create and active object using one port with two replications with one mapped to specific replication,
//		and the other mapped to any.
//	3)  Test the responses.
//  4)  Test that the message handler can send and invoke.
//  5)  Test successfull broadcast.

class ConstResponder : public ActiveObject::MessageHandler
{
public:
	ConstResponder(ActiveObject* parent) :
	  ActiveObject::MessageHandler(parent),
	  replyCount_(0)
	{}
	virtual bool handle(RoomMessageSP message)
	{
		MessageMap::iterator i = messageMap_.find(message->messageID());
		if (messageMap_.end() != i)
		{
			switch (replyCount_++ % 3)
			{
			case 0:
				message->reply(i->second.first, IPayload::ReleaseOwnership(IntPayloadSP(new IntPayload(i->second.second))));
				break;
			case 1:
				this->sendMessage(message->portIndex(), i->second.first, IPayload::ReleaseOwnership(IntPayloadSP(new IntPayload(i->second.second))));
				break;
			case 2:
				this->invoke(0, // Not waiting for a reply.
							 message->portIndex(),
							 i->second.first,
							 IPayload::ReleaseOwnership(IntPayloadSP(new IntPayload(i->second.second))),
							 false);
				break;
			}
		}
		return true;
	}
	void mapResponse(MessageID inMessage, MessageID outMessage, int outPayload)
	{
		ASSERT(messageMap_.end() == messageMap_.find(inMessage));
		messageMap_[inMessage] = std::make_pair(outMessage, outPayload);
	}
	typedef boost::shared_ptr<ConstResponder> Ptr;
private:
	typedef std::pair<MessageID, int> Response;
	typedef std::map<MessageID, Response> MessageMap;
	MessageMap messageMap_;
	size_t replyCount_;
};

class BCastResponder : public ActiveObject::MessageHandler
{
public:
	BCastResponder(ActiveObject* parent) :
	  ActiveObject::MessageHandler(parent)
	{}
	virtual bool handle(RoomMessageSP message)
	{
		this->sendMessage(message->portIndex().port(), GenericTestProtocol::OUT_VOID_MESSAGE1);
		return true;
	}
	typedef boost::shared_ptr<BCastResponder> Ptr;
};

void SendMessageErrorTestSuite::testMessageHandler(void)
{
	try
	{
		// Use a generic test object that does nothing without message handlers.
		TestActiveObjectSP tao(new TestActiveObject("tao"));

		// Create a message handler that maps void1 to (int1,1) and map it to port(1, 0).
		ConstResponder::Ptr mh1(new ConstResponder(tao.get()));
		mh1->mapResponse(GenericTestProtocol::IN_VOID_MESSAGE1, GenericTestProtocol::OUT_INT_MESSAGE1, 1);
		tao->mapMessageHandler(PortIndex(TestActiveInterface::PORT_MY_PORT_1, 0), mh1);

		// Create a message handler that maps void1 to (int2,2) and map it to port(1, all).
		ConstResponder::Ptr mh2(new ConstResponder(tao.get()));
		mh2->mapResponse(GenericTestProtocol::IN_VOID_MESSAGE1, GenericTestProtocol::OUT_INT_MESSAGE2, 2);
		tao->mapMessageHandler(TestActiveInterface::PORT_MY_PORT_1, mh2);

		// Create and map a broadcast responder to port 2.
		BCastResponder::Ptr mh3(new BCastResponder(tao.get()));
		tao->mapMessageHandler(TestActiveInterface::PORT_MY_PORT_2, mh3);

		// Create and wire event portals.
		EventPortalSP ep1(new EventPortal("ep1"));
		TEST_ASSERT(ep1->initialize());
		EventPortalSP ep2(new EventPortal("ep2"));
		TEST_ASSERT(ep2->initialize());
		EventPortalSP ep3(new EventPortal("ep3"));
		TEST_ASSERT(ep3->initialize());
		EventPortalSP ep4(new EventPortal("ep4"));
		TEST_ASSERT(ep4->initialize());
		BindingList bindings;
		bindings.push_back(PortalBindingSP(new PortalBinding(ep1, tao, TestActiveInterface::PORT_MY_PORT_1)));
		bindings.push_back(PortalBindingSP(new PortalBinding(ep2, tao, TestActiveInterface::PORT_MY_PORT_1)));
		bindings.push_back(PortalBindingSP(new PortalBinding(ep3, tao, TestActiveInterface::PORT_MY_PORT_2)));
		bindings.push_back(PortalBindingSP(new PortalBinding(ep4, tao, TestActiveInterface::PORT_MY_PORT_2)));

		// Now start the object and test for the expected response.
		TEST_ASSERT(tao->start(DWait));
		for (size_t i = 0; i < 9; ++i)
		{
			{
				ep1->sendMessage(GenericTestProtocol::IN_VOID_MESSAGE1);
				TEST_ASSERT(ep1->wait(DWait));
				RoomMessageSP message = ep1->getMessage();
				TEST_ASSERT(0 != message);
				TEST_ASSERT(GenericTestProtocol::OUT_INT_MESSAGE1 == message->messageID());
				SpiControl::Protocol::IntPayloadSP payload =
					boost::dynamic_pointer_cast<SpiControl::Protocol::IntPayload, IPayload>(message->releasePayload());
				TEST_ASSERT(0 != payload);
				TEST_ASSERT(1 == payload->value());
			}
			{
				ep2->sendMessage(GenericTestProtocol::IN_VOID_MESSAGE1);
				TEST_ASSERT(ep2->wait(DWait));
				RoomMessageSP message = ep2->getMessage();
				TEST_ASSERT(0 != message);
				TEST_ASSERT(GenericTestProtocol::OUT_INT_MESSAGE2 == message->messageID());
				SpiControl::Protocol::IntPayloadSP payload =
					boost::dynamic_pointer_cast<SpiControl::Protocol::IntPayload, IPayload>(message->releasePayload());
				TEST_ASSERT(0 != payload);
				TEST_ASSERT(2 == payload->value());
			}
			ep3->sendMessage(GenericTestProtocol::IN_VOID_MESSAGE1);
			{
				TEST_ASSERT(ep3->wait(DWait));
				RoomMessageSP message = ep3->getMessage();
				TEST_ASSERT(0 != message);
				TEST_ASSERT(GenericTestProtocol::OUT_VOID_MESSAGE1 == message->messageID());
			}{
				TEST_ASSERT(ep4->wait(DWait));
				RoomMessageSP message = ep4->getMessage();
				TEST_ASSERT(0 != message);
				TEST_ASSERT(GenericTestProtocol::OUT_VOID_MESSAGE1 == message->messageID());
			}
		}
	}
	catch (SpiFoundation::RoomFW::SendMessageError& e) // Messaging error.
	{
		G_LOG_0(SpiFoundation::RoomFW::LV_ERROR, e.what());
		TEST_ASSERT_MSG(false, e.what());
	}
	catch (std::runtime_error& e) // Binding error.
	{
		G_LOG_0(SpiFoundation::RoomFW::LV_ERROR, e.what());
		TEST_ASSERT_MSG(false, e.what());
	}
	catch (...) // What the heck error.
	{
		G_LOG_0(SpiFoundation::RoomFW::LV_ERROR, "Unhandled Exception");
		TEST_ASSERT_MSG(false, "Unhandled Exception");
	}
}

//
// Non-clone-able payload
//
class NCIntPayload : public IntPayload
{
public:
	NCIntPayload(int i) : IntPayload(i) {}
	virtual SpiFoundation::RoomFW::IPayloadSP clone() { return SpiFoundation::RoomFW::IPayloadSP(); }
};

//
// SendMessageError reasons test.
//
//	1)  Create a message handler that responds to a reason by generating it.
//	2)  Set up test object and send it each reason, verifying that it's errored with the specified reason.
//
/*
|-----------|
|   tao1    |
|          |1|-----|ep1|
|           |  \---|ep2|
|          |2|
|           |
|          |3|--\
|           |   |
|          |4|  |
|           |   |
|-----------|   |
				|
|-----------|   |
|   tao2    |   |
|          |1|-----|ep3|
|           |   |
|          |2|--/
|           |
|          |3|
|           |
|          |4|
|           |
|-----------|
*/
class ReasonGenerator : public ActiveObject::MessageHandler
{
public:
	ReasonGenerator(ActiveObject* parent) :
	  ActiveObject::MessageHandler(parent)
	{}
	static const DWORD DWait = 
#ifdef INFINITE_WAITS
		INFINITE;
#else
		500;
#endif
	virtual bool handle(RoomMessageSP message)
	{
		switch (message->messageID())
		{
		case GenericTestProtocol::IN_INT_MESSAGE1: // IN_INT_MESSAGE1 always starts the test.
			{
				IntPayloadSP payload = boost::dynamic_pointer_cast<IntPayload, IPayload>(message->releasePayload());
				if (0 == payload)
				{
					throw std::runtime_error("bad payload");
				}
				switch (payload->value())
				{
				case SendMessageError::SMER_INVALID_PORT:	// Invalid port index.
					this->sendMessage(PortIndex::INVALID, GenericTestProtocol::OUT_VOID_MESSAGE1);
					break;
				case SendMessageError::SMER_PORT_NOT_BOUND: // Valid port index, but not bound.
					this->sendMessage(TestActiveInterface::PORT_MY_PORT_2, GenericTestProtocol::OUT_VOID_MESSAGE1);
					break;
				case SendMessageError::SMER_RECEIVER_ERRORED: // The recipient of the message is in an errored state.
					this->sendMessage(TestActiveInterface::PORT_MY_PORT_3, GenericTestProtocol::IN_VOID_MESSAGE1);
					break;
				case SendMessageError::SMER_BROADCAST_PAYLOAD: // Attempt to broadcast a payload.  Not allowed.
					this->sendMessage(TestActiveInterface::PORT_MY_PORT_1, GenericTestProtocol::OUT_INT_MESSAGE1, IPayload::ReleaseOwnership(IntPayloadSP(new NCIntPayload(42))));
					break;
				case SendMessageError::SMER_BROADCAST_INVOKE:	// Attempt to broadcast an invoke.  Not allowed.
					this->invoke(DWait, TestActiveInterface::PORT_MY_PORT_1, GenericTestProtocol::OUT_VOID_MESSAGE1);
					break;
				case SendMessageError::SMER_CONTRACT_VIOLATION: // Message does not comply with contract (protocol) specified port's protocol.
					this->sendMessage(TestActiveInterface::PORT_MY_PORT_3, GenericTestProtocol::OUT_VOID_MESSAGE1);
					break;
				case SendMessageError::SMER_REPLY_TO_REPLY: // Attempt to reply to a message that was sent by reply.  Not allowed.
					this->invoke(DWait, TestActiveInterface::PORT_MY_PORT_3, GenericTestProtocol::IN_VOID_MESSAGE2)->reply(GenericTestProtocol::IN_VOID_MESSAGE2);
					break;
				case SendMessageError::SMER_INVOKE_TIMEOUT: // An invoke timed out and the call specified (or defaulted) to handle it with an exception.
					this->invoke(DWait, TestActiveInterface::PORT_MY_PORT_3, GenericTestProtocol::IN_VOID_MESSAGE3);
					break;
				default:
					throw std::runtime_error("bad reason");
				}
			}
			break;
		case GenericTestProtocol::IN_VOID_MESSAGE2: // Second object Reply.
			{
				if (message->portIndex().port() == TestActiveInterface::PORT_MY_PORT_2)
				{
					message->reply(GenericTestProtocol::OUT_VOID_MESSAGE3);
				}
			}
			break;
		// Ignore void message 3 for invoke timeout.
		}
		return true;	
	}
	typedef boost::shared_ptr<ReasonGenerator> Ptr;
private:
};

void SendMessageErrorTestSuite::testReasons(void)
{
	/*
	|-----------|
	|   tao1    |
	|          |1|-----|ep1|
	|           |  \---|ep2|
	|          |2|
	|           |
	|          |3|--\
	|           |   |
	|          |4|  |
	|           |   |
	|-----------|   |
					|
	|-----------|   |
	|   tao2    |   |
	|          |1|-----|ep3|
	|           |   |
	|          |2|--/
	|           |
	|          |3|
	|           |
	|          |4|
	|           |
	|-----------|
	*/
	try
	{
		// Active objects with reason generator handler.
		TestActiveObjectSP tao1(new TestActiveObject("tao1"));
		ReasonGenerator::Ptr mh1(new ReasonGenerator(tao1.get()));
		tao1->mapMessageHandler(PortIndex::INVALID, mh1);
		TestActiveObjectSP tao2(new TestActiveObject("tao2"));
		ReasonGenerator::Ptr mh2(new ReasonGenerator(tao2.get()));
		tao2->mapMessageHandler(PortIndex::INVALID, mh2);

		// Portals.
		EventPortalSP ep1(new EventPortal("ep1"));
		TEST_ASSERT(ep1->initialize());
		EventPortalSP ep2(new EventPortal("ep2"));
		TEST_ASSERT(ep2->initialize());
		EventPortalSP ep3(new EventPortal("ep3"));
		TEST_ASSERT(ep3->initialize());

		// Bindings.
		BindingList bindings;
		bindings.push_back(PortalBindingSP(new PortalBinding(ep1, tao1, TestActiveInterface::PORT_MY_PORT_1)));
		bindings.push_back(PortalBindingSP(new PortalBinding(ep2, tao1, TestActiveInterface::PORT_MY_PORT_1)));
		bindings.push_back(PortalBindingSP(new PortalBinding(ep3, tao2, TestActiveInterface::PORT_MY_PORT_1)));
		bindings.push_back(ActiveObjectsBindingSP(new ActiveObjectsBinding(tao1, TestActiveInterface::PORT_MY_PORT_3, tao2, TestActiveInterface::PORT_MY_PORT_2)));

		// Now start the objects and test for the expected response.
		for (size_t i = SendMessageError::SMER_NONE + 1; i < SendMessageError::SMER_COUNT; ++i)
		{
			std::string caseTxt = SendMessageError::ReasonText((SendMessageError::Reason)i);
			caseTxt += ": ";

			if (!tao1->isRunning())
			{
				TEST_ASSERT_MSG_PREFIX(tao1->start(DWait), caseTxt);
			}
			if (!tao2->isRunning())
			{
				TEST_ASSERT_MSG_PREFIX(tao2->start(DWait), caseTxt);
			}

			if (SendMessageError::SMER_RECEIVER_ERRORED == i)
			{
				ep3->sendMessage(GenericTestProtocol::IN_INT_MESSAGE1, IPayload::ReleaseOwnership(IntPayloadSP(new IntPayload(SendMessageError::SMER_INVALID_PORT))));
				TEST_ASSERT_MSG_PREFIX(tao2->wait(DWait), caseTxt);
			}

			ep1->sendMessage(GenericTestProtocol::IN_INT_MESSAGE1, IPayload::ReleaseOwnership(IntPayloadSP(new IntPayload(i))));

			TEST_ASSERT_MSG_PREFIX(tao1->wait(DWait), caseTxt);
			IErrorSP baseError = tao1->getError();
			TEST_ASSERT_MSG_PREFIX((0 != baseError), caseTxt);
			SendMessageErrorSP smError = boost::dynamic_pointer_cast<SendMessageError, IError>(baseError);
			TEST_ASSERT_MSG_PREFIX((0 != smError), (caseTxt + baseError->what()));
			TEST_ASSERT_MSG_PREFIX((smError->getReason() == i), (caseTxt + smError->what()));
		}
	}
	catch (SpiFoundation::RoomFW::SendMessageError& e) // Messaging error.
	{
		G_LOG_0(SpiFoundation::RoomFW::LV_ERROR, e.what());
		TEST_ASSERT_MSG(false, e.what());
	}
	catch (std::runtime_error& e) // Binding error.
	{
		G_LOG_0(SpiFoundation::RoomFW::LV_ERROR, e.what());
		TEST_ASSERT_MSG(false, e.what());
	}
	catch (...) // What the heck error.
	{
		G_LOG_0(SpiFoundation::RoomFW::LV_ERROR, "Unhandled Exception");
		TEST_ASSERT_MSG(false, "Unhandled Exception");
	}
}

void SendMessageErrorTestSuite::testPortals(void)
{
	try
	{
		// Use a generic test object that does nothing without message handlers.
		TestActiveObjectSP tao(new TestActiveObject("tao"));

		// Create a message handler that throws an exception when it gets a message
		ErrorResponder::Ptr mh1(new ErrorResponder(tao.get()));
		tao->mapMessageHandler(TestActiveInterface::PORT_MY_PORT_1, mh1);

		// Create a portal.
		EventPortalSP ep(new EventPortal("ep"));
		TEST_ASSERT(ep->initialize());

		// Verify unbound port error.
		bool didThrow = false;
		try
		{
			ep->sendMessage(GenericTestProtocol::IN_VOID_MESSAGE1);
		}
		catch (SendMessageError& e)
		{
			didThrow = true;
			TEST_ASSERT(e.getReason() == SendMessageError::SMER_PORT_NOT_BOUND);
		}
		TEST_ASSERT(didThrow);
		didThrow = false;
		try
		{
			ep->invoke(0, GenericTestProtocol::IN_VOID_MESSAGE1);
		}
		catch (SendMessageError& e)
		{
			didThrow = true;
			TEST_ASSERT(e.getReason() == SendMessageError::SMER_PORT_NOT_BOUND);
		}
		TEST_ASSERT(didThrow);

		// Bind portal and verify contract violation.
		PortalBinding binding(ep, tao, TestActiveInterface::PORT_MY_PORT_1);
		didThrow = false;
		try
		{
			ep->sendMessage(GenericTestProtocol::OUT_VOID_MESSAGE1);
		}
		catch (SendMessageError& e)
		{
			didThrow = true;
			TEST_ASSERT(e.getReason() == SendMessageError::SMER_CONTRACT_VIOLATION);
		}
		TEST_ASSERT(didThrow);
		didThrow = false;
		try
		{
			ep->invoke(0, GenericTestProtocol::OUT_VOID_MESSAGE1);
		}
		catch (SendMessageError& e)
		{
			didThrow = true;
			TEST_ASSERT(e.getReason() == SendMessageError::SMER_CONTRACT_VIOLATION);
		}
		TEST_ASSERT(didThrow);

		// Start and error the object.
		TEST_ASSERT(tao->start(DWait));
		try
		{
			ep->sendMessage(GenericTestProtocol::IN_VOID_MESSAGE1);
		}
		catch (SendMessageError& e)
		{
			TEST_ASSERT_MSG(false, e.what());
		}
		TEST_ASSERT(tao->wait(DWait));

		// Verify receiver errored exceptions.
		didThrow = false;
		try
		{
			ep->sendMessage(GenericTestProtocol::IN_VOID_MESSAGE1);
		}
		catch (SendMessageError& e)
		{
			didThrow = true;
			TEST_ASSERT(e.getReason() == SendMessageError::SMER_RECEIVER_ERRORED);
		}
		TEST_ASSERT(didThrow);
		didThrow = false;
		try
		{
			ep->invoke(0, GenericTestProtocol::IN_VOID_MESSAGE1);
		}
		catch (SendMessageError& e)
		{
			didThrow = true;
			TEST_ASSERT(e.getReason() == SendMessageError::SMER_RECEIVER_ERRORED);
		}
		TEST_ASSERT(didThrow);
	}
	catch (SpiFoundation::RoomFW::SendMessageError& e) // Messaging error.
	{
		G_LOG_0(SpiFoundation::RoomFW::LV_ERROR, e.what());
		TEST_ASSERT_MSG(false, e.what());
	}
	catch (std::runtime_error& e) // Binding error.
	{
		G_LOG_0(SpiFoundation::RoomFW::LV_ERROR, e.what());
		TEST_ASSERT_MSG(false, e.what());
	}
	catch (...) // What the heck error.
	{
		G_LOG_0(SpiFoundation::RoomFW::LV_ERROR, "Unhandled Exception");
		TEST_ASSERT_MSG(false, "Unhandled Exception");
	}
}

}}}
